# 修复财报pdf目录

import os
import fitz  # PyMuPDF  
import re


i_invalid_pagecount = 0 #没有页码的页面数有几个，有些报表第一页是单纯的封面，是没有页码的，导致pagenum的指示器的页码比实际页码少1页，
i_invalid_childpage_offset = 0 #子目录的位移
is_special_caiwu_bb = False #是否像“青岛啤酒”这种SB的财务报表格式,默认为普通正规格式

def get_pdf_bookmarks(input_pdf_path):  
    doc = fitz.open(input_pdf_path)
    existing_toc = doc.get_toc()
    if not existing_toc:  
        print("No bookmarks found in the PDF.")
        return
    for idx, toc_entry in enumerate(existing_toc, 1):  
        level, title, page_num = toc_entry 
        print(f"书签{idx}：Level {level}: {title} - Page {page_num}") 

def fix_pdf_bookmarks(input_pdf_path, output_pdf_path):  
    # 打开PDF文件 
    doc = fitz.open(input_pdf_path)  
      
    # 获取现有的书签列表  
    # existing_bookmarks = doc.get_bookmarks() # get_bookmarks方法不存在

    existing_toc = doc.get_toc()
    # 如果没有书签，则直接返回或进行其他处理  
    if not existing_toc:  
        print("No bookmarks found in the PDF.")  

    # 两种方式都能获取页面总数
    print("pageLen=",len(doc)) #页面总数
    print("pageCount=",doc.page_count) #页面总数 
    
    
    # 遍历书签列表，并进行修改（这里只是打印出来作为示例）  
    for idx, toc_entry in enumerate(existing_toc, 1):  
        level, title, page_num = toc_entry 
        # level表示目录结构1为根目录，2为子目录，3为子子目录，依次往下
        # 例如5级目录分别表示: 
        """ 
        Level 1: 第三节 管理层讨论与分析 - Page 9
        Level 2: 五、 报告期内主要经营情况 - Page 12
        Level 3: (一) 主营业务分析 - Page 12
        Level 4: 1. 收入和成本分析 - Page 13
        Level 5: (1). 主营业务分行业、分产品、分地区、分销售模式情况 - Page 13
        """

        print(f"旧书签{idx}：Level {level}: {title} - Page {page_num}")  
        #错误的目录格式如： Level 1: OLE_LINK3 - Page 41

    print(f'\n{input_pdf_path}')
    
    new_toc = []  #新书签
    ml_is_begn = False
    for pageindex in range(doc.page_count):
        text = doc.get_page_text(pageindex) #页面是从0开始第1页面
        pagetext = text.split('\n \n') #每一页都有一个页眉页脚'2023 年年度报告 \n4 / 209 '与正文用\n \n分隔，
        #第pagetext[0]为每页的页眉信息，pagetext[1]为正文主体部分
        if len(pagetext)>0:
            for linetext in pagetext:
                if len(linetext)>50 and ("......." in linetext or "…………" in linetext):
                    pagecontent = linetext.split('\n')
                    break
            else:
                continue
            # if len(pagetext)>1 and len(pagetext[1])>50:
            #     pagecontent = pagetext[1].split('\n') #目录一般在pagetext[1]中
            # else:
            #     pagecontent = pagetext[0].split('\n') #也有可能在pagetext[0]中(如鱼跃医疗2021),还有可能在pagetext[2]中(如海康2021)
            #拆分后，pagecontent[0]有可能是"目录"，也有可能是" ",所以需要enumerate得到目录所在的ilistindex
            pre_titles = []
            for ilistindex,pertitle in enumerate(pagecontent):
                mltitle = pertitle.replace(" ", "") #还有标题是'目  录 '这种的情况
                if not ml_is_begn:
                    if "..." in mltitle or "………" in mltitle:
                        # 还有可能'目录'两个字在pagetext[0]中，而实际目录又在pagetext[1]中(如海康2023)
                        # 如：'致股东............1'
                        ml_is_begn = True
                        iline = ilistindex #此时一般情况下 ilistindex + 1为：第一节
                        # 此时...前可能有汉字标题，如果没有，则是一句为汉字标题
                        chinese_only = query_chineses_in_line(mltitle)
                    elif mltitle.strip() == "目录":
                        ml_is_begn = True
                        iline = ilistindex + 1
                        pre_titles.clear()
                    else: #首个可能为：致股东,没有...
                        # ml_is_begn = True
                        pre_titles.append(mltitle)
                        # iline = ilistindex + 1

                if ml_is_begn : #我们只需要读取目录页的目录即可，根据文档本身的目录页信息，来生成新的目录索引
                    new_item = [1, "目录", pageindex+1]
                    new_toc.append(new_item)
                    print("目录:",new_item)

                    
                    while iline+1 <= len(pagecontent): #分析原文目录页所列目录结构
                        # 一般情况下列表格式为 0:目录，1：第一节，2：释义，3：...，4：3，5：第二节，6：公司简介，7：....，8：5，
                        # 但还有情况：0:目录，1：第一节，2：释义....，3：3，4：第二节，
                        # 还有可能：  0:目录，1：第一节，2：释义....3，3:第二节
                        # 还有可能：  0:目录，1：重要事项....，2：1，3:目 录......,4:2, 5:第一章  公司简介.....,6:7,
                        # 还有可能：  0:致股东............1,1:第一节 释义......3
                        # 所以从iline开始，读取并解析每一行目录内容, 普通情况下iline+1为页面标题，iline+2为分隔符，iline+3为页码，然后依次读取
                        # 但也在不规格的情况，0:目录，1：第一节，2：释义 ...，3：3，4：第二节，5：公司简介，6：....，7：5
                        # 标题有: '公司简介和主要财务指标 '，也有："释义 .....", 也有："管理层讨论与分析....."，还有'管理层讨论与分析..... 11 ' 等四种情况
                        
                        # 也就是有可能 标题和分隔符...是在同一行，
                        # 还有更特殊的目录，比如：重庆啤酒2023
                        tmp_title = pagecontent[iline].strip() #第一节，或重要事项....，或重要事项....1,
                        i_list_id = 1
                        if "..." in tmp_title or "………" in tmp_title:
                            # 先判断有无数字，且是末尾的数字(表示是页码信息)，
                            chinese_only = re.findall(r'[\u4e00-\u9fa5]+', tmp_title) #直接采用获取汉字的正则
                            if len(chinese_only)>0:
                                pre_titles.append(' '.join(chinese_only))
                            title = ' '.join(pre_titles) #只要出现了....表示可以处理本目录项了，将缓存的内容连起来做为标题

                            numbers = re.findall(r'\d+', tmp_title)
                            if len(numbers)>0: 
                                # 开头的数字不算页码，而是子标题序号，如: '3.1 总体经营情况...' , 如果是 '3.1 总体经营情况...23'这种情况咋搞
                                if tmp_title.lstrip().startswith(numbers[0]): 
                                    s_id = '.'.join(numbers)
                                    title = s_id + ' ' + title
                                    next_line = pagecontent[iline+1].strip()
                                    ipage_num = int(next_line)
                                    i_list_id = 2 #表示是子目录
                                    iline += 2
                                else:
                                    # 有...,且有页码，表示本行即是一个目录项
                                    ipage_num = int(numbers[0])
                                    iline += 1
                            else:
                                # 只有...没有序号，表示一行为标题，下一行必为序号
                                next_line = pagecontent[iline+1].strip()
                                ipage_num = int(next_line)
                                iline += 2
                            # if title.endswith('财务报表及附注'):
                            #     new_item = [i_list_id, title, 143]
                            # else:    
                            new_item = [i_list_id, title, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("目录:",new_item)
                            pre_titles.clear()
                            if title == "目录" and len(new_toc)>1: #目录页中本身有'目录'这个索引,则前面手动添加的要去掉,避免重复
                                del new_toc[0]
                        else:
                            pre_titles.append(tmp_title.strip())
                            iline += 1 #如果是：第一节, 释义,这样的，先依次缓存，直到找到了...，才把缓存的数组串起来做为本目录的标题，
                    break #只处理目录页
        
        if len(new_toc)>1:
            # 表示已经找到目录所在页，并生成了新目录,则不再往下找目录页了
            # 分析新生成的目录页，找到“管理层讨论与分析”与"财务报告"这两节，并进一步获取子目录
            manager_toc = []
            report_toc = []
            imanager_index = 0
            ireport_index = 0
            for index,toc in enumerate(new_toc):
                title = toc[1].strip()
                ibegin_page_num = toc[2] #是已保存目录页码，此页码已经包含了offset页面偏移量！！
                if title.startswith("管理层讨论与分析") or title.endswith("管理层讨论与分析") or title.startswith("经营情况讨论与分析") or title.endswith("经营情况讨论与分析"):
                    imanager_index = index
                    iend_page_num = new_toc[index+1][2] #因为已知“管理层讨论与分析”后面肯定还有下一节目录，故懒得判断是否还有下一节
                    manager_toc = find_manager_list(doc,ibegin_page_num-1,iend_page_num-1) 
                    #每一节的页面都是重新起一个页面的，所以iend_page_num是下一节的起始页，那么上一节的内容页肯定不在此页上，故上一节内容结尾页码必是iend_page_num-1
                elif title.startswith("财务报告") or title.endswith("财务报告") or title.endswith('财务报表及附注'):
                    ireport_index = index
                    if index+1 < len(new_toc):
                        iend_page_num = new_toc[index+1][2]
                        report_toc = find_caiwureport_list(doc,ibegin_page_num-1,iend_page_num-1)
                    else:
                        report_toc = find_caiwureport_list(doc,ibegin_page_num-1,doc.page_count)

            
            if len(report_toc)>0:
                new_list = new_toc[:ireport_index+1] + report_toc + new_toc[ireport_index+1:]
                new_toc = new_list
            if len(manager_toc)>0:
                new_list = new_toc[:imanager_index+1] + manager_toc + new_toc[imanager_index+1:]
                new_toc = new_list
            
            break

    if len(new_toc)>0: 
        #分析得到了正确的目录结构
        doc.set_toc(new_toc)
        print("已生成新目录索引")
        # 保存修改后的PDF
        doc.save(output_pdf_path)
        print("已保存至：",output_pdf_path)

    # 关闭文档  
    doc.close()

""" 
直接手动设置主要目录  
"""
def manufix_pdf_bookmarks(input_pdf_path, output_pdf_path):  
    # 打开PDF文件  
    doc = fitz.open(input_pdf_path)  
    # 获取现有的书签列表  
    # existing_bookmarks = doc.get_bookmarks() # get_bookmarks方法不存在

    existing_toc = doc.get_toc()
    # 如果没有书签，则直接返回或进行其他处理  
    if not existing_toc:  
        print("No bookmarks found in the PDF.")  

    # 两种方式都能获取页面总数
    print("pageLen=",len(doc)) #页面总数
    print("pageCount=",doc.page_count) #页面总数 
    
    new_toc = []  #新书签

    # 手动添加的话则：
    new_item = [1,"目录",2+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第一节 释义",4+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第二节 公司简介和主要财务指标",5+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第三节 管理层讨论与分析",10+i_invalid_pagecount]
    new_toc.append(new_item)
    manager_toc = find_manager_list(doc,10+i_invalid_pagecount-1,32+i_invalid_pagecount)
    if len(manager_toc)>0:
        new_toc += manager_toc
    
    new_item = [1,"第四节 公司治理",32+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第五节 环境与社会责任",47+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第六节 重要事项",54+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第七节 股份变动及股东情况",66+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第八节 优先股相关情况",72+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第九节 债券相关情况",74+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [1,"第十节 财务报告",76+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [2,"合并资产负债表",80+i_invalid_pagecount]
    new_toc.append(new_item)
    new_item = [2,"母公司资产负债表",84+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [2,"合并利润表",87+i_invalid_pagecount]
    new_toc.append(new_item)
    new_item = [2,"母公司利润表",89+i_invalid_pagecount]
    new_toc.append(new_item)
    
    new_item = [2,"合并现金流量表",91+i_invalid_pagecount]
    new_toc.append(new_item)
    new_item = [2,"母公司现金流量表",93+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [2,"合并所有者权益变动表 ",95+i_invalid_pagecount]
    new_toc.append(new_item)
    new_item = [2,"母公司所有者权益变动表",99+i_invalid_pagecount]
    new_toc.append(new_item)

    new_item = [2,"现金流量表补充资料",171+i_invalid_pagecount] 
    new_toc.append(new_item)

    icaiwu_memo_page = 120
    new_item = [2,"合并财务报表项目注释",icaiwu_memo_page+i_invalid_pagecount] 
    new_toc.append(new_item)
    lines = doc.get_page_text(icaiwu_memo_page).split('\n')
    indices_and_lines = [(index, line) for index,line in enumerate(lines) if "合并财务报表项目注释" in line]
    if len(indices_and_lines)>0: 
        # [(56, '合并财务报表项目注释')]
        for index,line in indices_and_lines:
            print(f"{index},{line}") #56,合并财务报表项目注释
            left_lines = lines[index+1:]
            caiwu_memos = find_caiwu_memo_list(doc,icaiwu_memo_page,left_lines,is_special_caiwu_bb)
            if len(caiwu_memos)>0:
                new_toc += caiwu_memos
            break
    
    # 保存修改后的PDF
    if len(new_toc)>0:
        doc.set_toc(new_toc)
        print("已生成新目录索引")
        doc.save(output_pdf_path)
        print("已保存至：",output_pdf_path)

    # 关闭文档  
    doc.close()



# 获取1、2、3、等序号列表
def query_queneids_in_list(lines,i):
    return [(index,line) for index,line in enumerate(lines) if line.lstrip().startswith(f"{i}、")]

# 获取(1),(2),(3)等序号列表
def query_kuohaoids_in_list(lines,i):
    return [(index,line) for index,line in enumerate(lines) if line.lstrip().startswith(f"({i})")]

# 获取指定序号列表
def query_otherids_in_list(lines,idstr):
    return [(index,line) for index,line in enumerate(lines) if line.lstrip().startswith(idstr)]

# 获取字符串中的汉字串(汉字包括 一、二)
def query_chineses_in_line(line):
    return re.findall(r'[\u4e00-\u9fa5]+', line)

# 获取字符串中的数字串
def query_numbers_in_line(line):
    return re.findall(r'\d+', line)

""" 
财务报表注释,
icaiwu_memo_page_index 即为“合并财务报表项目注释”所在页的页码index，
即doc.get_page_text(icaiwu_memo_page_index)里含有”合并财务报表项目注释“
iend_page_num查询终止页，即”合并财务报表项目注释“下一个目录项所在页面页码
"""
def find_caiwu_memo_list(doc,icaiwu_memo_page_index,iend_page_num,left_lines,is_special_memo):
    #从当前页面"合并财务报表项目注释"之后的剩余内容开始查找财务注释项目的子项目
    # 子目录格式有以下几种： 
    # '1、 货币资金 '，包含 "i、"的标题，即为子目录的标题 
    # '(1) 货币资金 '，包含 "(i) "的标题，即为子目录的标题 
    # '(一) 货币资金 '，包含 "(一) "的标题，即为子目录的标题 
    # 并且产2个项目，必定是'货币资金'和'交易性金融资产'
    # (特殊的报表又不是这个格式，比如平安银行2023)
    #在本页下面就有子目录
    
    def is_start_with_numbered_3(s_line):  
        # 定义一个正则表达式，匹配以(一|(二|(三|(四|(五|(六|(七|(八|(九|(十 开头的字符串  
        # 有可能(十一、(十二、这样，所以后面的右括号不要匹配，否则不好写
        pattern = r'^\((一|二|三|四|五|六|七|八|九|十)'
        match = re.match(pattern, s_line)  
        if match:  
            return True  
        else:  
            return False
        
    # 先从left_lines查找'货币资金'，确定其序号类型，
    # 然后再根据序号查找剩余的
    def find_first_title_id(page_lines,first_pagenum):
        i_childid_type = 1 #类型：1为'1、 货币资金 '，2为(1) 货币资金，3为(一) 货币资金
        lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.rstrip().endswith('货币资金')]
        if len(lines_with_target)>0:
            index = int(lines_with_target[0][0])
            line_title = lines_with_target[0][1].strip()
            if len(line_title)>len('货币资金'):
                if line_title.startswith('1、'):
                    i_childid_type = 1
                elif line_title.startswith('1.'):
                    i_childid_type = 4
                elif line_title.startswith('(1)'):
                    i_childid_type = 2
                elif line_title.startswith('(一)'):
                    i_childid_type = 3
                else:
                    i_childid_type = 0
                print(f"find.first.childid.type:{i_childid_type}:{line_title}")

                if i_childid_type > 0:
                    new_item = [3,line_title,first_pagenum] 
                    new_toc.append(new_item)
                    print("报表项目注释子目录：",new_item)
            elif index>0:
                # 说明序号在上一行
                s_id = page_lines[index-1].strip() #还有1.这种情况
                if s_id == '1、':
                    i_childid_type = 1
                elif s_id == '1.':
                    i_childid_type = 4
                elif s_id == '(1)':
                    i_childid_type = 2
                elif s_id == '(一)':
                    i_childid_type = 3
                else:
                    i_childid_type = 0
                print(f"find.first.childid.type[{i_childid_type}]:{s_id} {line_title}")

                if i_childid_type > 0:
                    new_item = [3,s_id+line_title,first_pagenum]
                    new_toc.append(new_item)
                    print("报表项目注释子目录：",new_item)
            else:
                i_childid_type = 9
                print("notfound.child.id.type:",line_title)

            # page_lines = page_lines[index+1:] #!!!这样写，不会改变原始列表
            # 在Python中，当你将一个列表作为参数传递给一个函数时，你实际上是在传递这个列表的引用，而不是列表的副本。
            # 因此，在函数内部对列表所做的任何修改（例如使用 del 删除元素）都会直接反映到原始的列表上
            # 如果此时 del page_lines[0] ,删除后，原始page_lines值会改变
            # 但若像上面这样page_lines = page_lines[index+1:]，那么参数page_lines本不会改变
            # 因为此句的意思是=号前page_lines，是一个新对象变量，等同于将page_lines[index+1:]的赋值给了一个新的对象page_lines
            # 如果直接改变参数的page_lines，则可以：
            del page_lines[:index+1]
            # 或者使用切片
            # page_lines[:] = page_lines[index+1:]
            
            return i_childid_type
        else:
            return 0 #没找到货币资金

    def record_ok_item(lines_with_target,lines,item_page_num):
        index = int(lines_with_target[0][0])
        line_title = lines_with_target[0][1].strip()
        chinese_only = query_chineses_in_line(line_title)
        if len(chinese_only)>0:
            item_title = line_title
            del lines[:index+1]
        elif index+1 < len(lines): #否则标题就在下一行
            item_title = line_title + lines[index+1].strip()
            del lines[:index+2]
        else:
            # 标题在下一页，这是最操蛋的了，暂只显示序号（这种情况应该极少）
            print("title.is.in.nextpage！jjboom！！！")
            item_title = line_title 
            lines.clear() #直接进入下一页

        new_item = [3,item_title,item_page_num]
        new_toc.append(new_item)
        print("报表项目注释子目录：",new_item)
    
    # 从剩下的page_lines及往后页面，查找其他子项
    def analyze_page_lines(page_lines,i_childid_num,item_page_num):
        while len(page_lines)>0:
            if 1 == i_childid_type:
                lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"{i_childid_num}、")]
                # 得到：[(1,'2、 交易性金融资产 ')],还有可能为[(1,'2、 ')]
                if len(lines_with_target)>0:
                    record_ok_item(lines_with_target,page_lines,item_page_num)
                    i_childid_num +=1
                else:
                    break #剩余内容里已没有适配项，查找下一页
            elif 4 == i_childid_type:
                lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"{i_childid_num}. ")]
                # 得到：[(1,'2. 交易性金融资产 ')],还有可能为[(1,'2. ')] 
                # 傻逼的‘中兴通讯’还可能为：4A. 应收账款/应收账款保理，4B. 应收款项融资，然后再又：5. 预付款项
                if 0 == len(lines_with_target):
                    lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"{i_childid_num}A. ")]
                    if 0 == len(lines_with_target):
                        lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"{i_childid_num}B. ")]
                        if 0 == len(lines_with_target):
                            lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"{i_childid_num}C. ")]
                            if 0 == len(lines_with_target):
                                lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"{i_childid_num}D. ")]
                    
                if len(lines_with_target)>0:
                    record_ok_item(lines_with_target,page_lines,item_page_num)
                    i_childid_num +=1
                else:
                    break #剩余内容里已没有适配项，查找下一页   
            elif 2 == i_childid_type:
                lines_with_target = [(index,line) for index,line in enumerate(page_lines) if line.lstrip().startswith(f"({i_childid_num})")]
                # 得到：[(1,'(2) 交易性金融资产 ')],还有可能为[(1,'(2) ')]
                if len(lines_with_target)>0:
                    record_ok_item(lines_with_target,page_lines,item_page_num)
                    i_childid_num +=1
                else:
                    break #剩余内容里已没有适配项，查找下一页
            elif 3 == i_childid_type:
                # 以(-)(二)开头的子目录，此种更简单，会在本页中一次性找出所有(-)开头的子目录
                lines_with_target = [(index,line) for index,line in enumerate(page_lines) if is_start_with_numbered_3(line.strip())]
                if len(lines_with_target)>0: #[(5, '(二) '), (7, '(三) ')]
                    for target in lines_with_target:
                        line_index = target[0]
                        line_title = target[1].strip()
                        if len(line_title) > 5: #至少3+2 (二)备注
                            # 表示有标题
                            new_item = [3,line_title,item_page_num]
                        elif line_index+1<len(page_lines):
                            # 标题在下一行
                            new_item = [3,line_title+page_lines[line_index+1],item_page_num]
                        else:
                            # 标题在下一页，操蛋了,暂不要标题了（这种情况极少）
                            print("title.is.in.nextpage！jjboom！！！")
                            new_item = [3,line_title,item_page_num]

                        new_toc.append(new_item)
                        print("报表项目注释子目录：",new_item)
                        i_childid_num +=1

                break #不管本页有没有找到，都直接下一页
        return i_childid_num #参数i_childid_num只是个副本，在方法内部改变，不会像列表那样影响外部原始对象

    # i_num = 1 #序号  
    new_toc = []

    i_offset = i_invalid_childpage_offset if i_invalid_childpage_offset > 0 else 1
    i_page_num = icaiwu_memo_page_index + i_offset

    page_lines = left_lines
    i_childid_type = find_first_title_id(page_lines,i_page_num) #函数内会对page_lines本身做出修改
    i_childid_num = 2 #从第个序号开始往下查的
    if 9 == i_childid_type: #找到了货币资金，却没有适配的序号，则出错
        print("err: not.find.childid")
        return
    elif 0 == i_childid_type: #没找到‘货币资金’，往后面页面查找，基本上这种情况极少
        icaiwu_memo_page_index += 1
        i_page_num = icaiwu_memo_page_index + i_offset
        page_lines = doc.get_page_text(icaiwu_memo_page_index).split('\n')
        #第2页必定有，如果还没有说明首个项目不是叫‘货币资金’或其错误
        i_childid_type = find_first_title_id(page_lines,i_page_num) 
        if 9 == i_childid_type: #找到了货币资金，却没有适配的序号，则出错
            print("err: not.find.childid")
            return
        elif 0 == i_childid_type:
            print("err: not.find.货币资金")
            return
        # else:
            # 如果找到‘货币资金’还要对剩余的page_lines进行查找下一个

    # 对‘货币资金’所在页剩余内容分析下一个序号
    if len(page_lines)>0:
        # 找到了首个序号，再判断有没有剩余内容，继续查找下一个序号
        i_childid_num = analyze_page_lines(page_lines,i_childid_num,i_page_num)
        
    iend = min(iend_page_num,doc.page_count)
    icaiwu_memo_page_index += 1 #然后再从下一页开始循环查找剩余下的子目录
    for ipage in range(icaiwu_memo_page_index,iend):#页面是从0开始的第1页面，故下一页要从icaiwu_memo_page开始
        page_lines = doc.get_page_text(ipage).split('\n')
        i_page_num = ipage + i_offset
        i_childid_num = analyze_page_lines(page_lines,i_childid_num,i_page_num)

    return new_toc

""" 
管理层分析子目录,
ipage_index已经是页面index(已经包含目录所在页的偏移量)
iend_page_num是下一节的起始页码(只需搜索到此页即可)
"""
def find_manager_list(doc,ipage_index,iend_page_num):
# 一、经营情况讨论与分析
# 二、报告期内公司所处行业情况
# 三、报告期内公司从事的业务情况
# 四、报告期内核心竞争力分析
# 五、报告期内主要经营情况
# 六、公司关于公司未来发展的讨论与分析
# 七、公司因不适用准则规定或国家秘密、商业秘密等特殊原因，未按准则披露的情况和原因说明
    child_titles = ['经营情况讨论与分析','报告期内公司所处行业情况','报告期内公司从事的业务情况',
                    # '总体经营情况','财务报表分析','主要业务讨论与分析','经营中关注的重点','业务创新','风险管理','未来展望', #平安里是这种子目录结构
                    '报告期内核心竞争力分析','报告期内主要经营情况','公司关于公司未来发展的讨论与分析',
                    '公司因不适用准则规定或国家秘密、商业秘密等特殊原因，未按准则披露的情况和原因说明',]
    child_ids = ['一、','二、','三、','四、','五、','六、','七、','八、','九、','十、','十一、','十二、','十三、','十四、','十五、',]
    # 子目录正常情况：一、经营情况讨论与分析，二、报告期内公司所处行业情况
    # 子目录的少数可能情况：
    # 有可能子目录没有一、 二、这样的序号，此时标题必在child_titles里,且第一个必为'经营情况讨论与分析'
    # 有可能有序号一、 二、，但标题可能不在child_titles中,如美的2019：一、 概述

    # 有一种可能，节页面标题也为'经营情况讨论与分析',还可能节页面没有标题，只是图片
    
    new_toc=[]

    is_over = False
    i_offset = i_invalid_childpage_offset if i_invalid_childpage_offset > 0 else 1

    # 先以序号优先查找，1、2个页面，如果没有出现序号，就break，转到后面的标题查找,如果有就序号查找下去
    for ipage in range(ipage_index,iend_page_num):
        lines = doc.get_page_text(ipage).split('\n')
       
        while len(child_ids)>0 and len(lines)>0:
            numbers_with_target = query_otherids_in_list(lines,child_ids[0])
            if len(numbers_with_target) > 0:
                del child_ids[0]
                # 结果可能为 [(3, '一、xx ')] 也可能为[(3, '一、')]
                # 本index有标题，或者下一行才有标题
                iline = numbers_with_target[0][0]
                id_title = numbers_with_target[0][1]
                if len(id_title.strip())>3:
                    new_item = [2,id_title,ipage+i_offset] 
                    new_toc.append(new_item)
                    print("管理层分析子目录：",new_item)
                    del lines[:iline+1]
                elif iline+1 < len(lines): #标题在下一行
                    inext = iline+1
                    item_title = lines[inext].strip()
                    new_item = [2,id_title + item_title,ipage+i_offset]
                    new_toc.append(new_item)
                    print("管理层分析子目录：",new_item)
                    del lines[:inext+1]
                # else: #暂不考虑
            else:
                break #本页没有则下一页
        if ipage - ipage_index > 0 and len(new_toc) == 0: 
            # 本页和下一页面内，如果没有出现序号，就说明非序号类子目录，则break，改由下面的标题查找
            break

    if len(new_toc)==0:
        for ipage in range(ipage_index,iend_page_num):
            lines = doc.get_page_text(ipage).split('\n')
            ilistindex = 0
            while len(child_titles)>0 and ilistindex<len(child_titles): 
                title = child_titles[ilistindex]
                # 两种格式：经营情况讨论与分析 或 一、经营情况讨论与分析，同时要排除目录标题'第四节 经营情况讨论与分析'
                lines_with_target = [(index,line) for index,line in enumerate(lines) if line.rstrip().endswith(title) and not line.lstrip().startswith("第")]
                # 得到：[(3, '一、经营情况讨论与分析')], 还有可能为：[(4, '经营情况讨论与分析  '), (5, '一、经营情况讨论与分析 ')] #第一个是目录标题
                if len(lines_with_target)>0:
                    item_title = lines_with_target[-1][1] #取末尾的
                    new_item = [2,item_title,ipage+i_offset] #页码数要page=index+1，但如果i_invalid_pagecount>0的情况下，则不需要+1
                    new_toc.append(new_item)
                    del child_titles[ilistindex]
                    print("管理层分析子目录：",new_item)
                    if item_title.rstrip().endswith("公司因不适用准则规定或国家秘密、商业秘密等特殊原因，未按准则披露的情况和原因说明"):
                        is_over = True
                        break

                    iLast = int(lines_with_target[0][0])
                    if len(lines) > iLast:
                        del lines[:iLast+1] #从剩余内容后面找
                else:
                    ilistindex += 1 #有时候子目录顺序并不一定对，或者并不一定有这项子目录
                    
            if is_over or len(child_titles) == 0:
                break

    return new_toc


""" 
重要事项子目录   
"""
def find_important_list(doc,icaiwu_memo_page,left_lines):
# 一、承诺事项履行情况
# 二、报告期内控股股东及其他关联方非经营性占用资金情况
# 三、违规担保情况
# 四、公司董事会对会计师事务所“非标准意见审计报告”的说明
# 五、公司对会计政策、会计估计变更或重大会计差错更正原因和影响的分析说明
# 六、聘任、解聘会计师事务所情况
# 七、面临退市风险的情况
# 八、破产重整相关事项
# 九、重大诉讼、仲裁事项
# 十、上市公司及其董事、监事、高级管理人员、控股股东、实际控制人涉嫌违法违规、受到处罚
# 及整改情况
# 十一、报告期内公司及其控股股东、实际控制人诚信状况的说明
# 十二、重大关联交易
# 十三、重大合同及其履行情况
# 十四、募集资金使用进展说明
# 十五、其他对投资者作出价值判断和投资决策有重大影响的重大事项的说明
    new_toc=[]
    return new_toc

""" 
财务报告子目录
ipage_index已经是页面index（已经包含目录所在页的偏移量）
iend_page_num为本节内容页的末尾页码
"""
def find_caiwureport_list(doc,ipage_index,iend_page_num): 
    #  财务报表 后面搜索以下标题即可，就可以不用写死这几个项目的页码了，或者是直接解析财务报告里的一、二、这种格式子目录
    # 出现财务报告时，生成常见子目录
    # ipage_num即为财务报告页面,读取该页面，然后往后读，得到一、审计报告，二、财务报表...等子目录
    # 财务报表中的主要标题项
    # 36:'七、 '
    # 37:'合并财务报表项目注释 ' #居然是换行的，ca
    child_nums = ['一、','二、','三、','四、','五、','六、','七、','八、','九、','十、','十一、'
                    ,'十二、','十三、','十四、','十五、','十六、','十七、','十八、','十九、','二十、'
                    ,'二十一、','二十二、','二十三、','二十四、','二十五、','二十六、','二十七、','二十八、',]
    # 有了财报表，并且出现’基本情况‘表示始进入了报表附注项目
    child_titles = ['审计报告','财务报表','基本情况','财务报表的编制基础',
                    '重要会计政策及会计估计','重要会计政策和会计估计','主要会计政策和会计估计',
                    '税项',
                    '合并财务报表项目注释','合并财务报表项目附注','合并财务报表重要项目注释','合并财务报表主要项目注释',]
    behind_hbfz_titles = ['研发支出','合并范围的变更','合并范围的变动','在其他主体中的权益','政府补助','与金融工具相关的风险','公允价值的披露',
                    '关联方及关联交易','关联方关系及其交易','股份支付','承诺及或有事项','资产负债表日后事项','资产负债表日后事项说明',
                    '其他重要事项','母公司财务报表主要项目注释','补充资料',] 
    
    #确定’合并财务报表项目注释‘之后会出现的标题，非序号类型如果提前出现了则要放弃
    
    
    # 财务报告目录几种不同的情况：
    # 1）一、审计报告，二、财务报表(下有：1、合并资产负债表，2、母公司资产负债表...)，三、公司基本情况...
    # ..七、合并财务报表主要项目注释,...有序号依次列举，没有‘财务报表附注’进行分项,
    # eg:紫光国微、乐普医疗、养元等(多数为此)
    # 2）审计报告(居中标题，没有序号)、财务报表(居中标题，没有序号，一般没有本项)、合并资产负债表、合并利润表、
    # 合并股东权益变动表、合并现金流量表，然后是财务报表附注
    # 财务报表附注下开始分序号标题：一、 基本情况(或为：一 公司基本情况)，
    # 二、 财务报表的编制基础(或为：二 主要会计政策和会计估计)...五、 合并财务报表主要项目注释...
    # eg:中兴通讯、青岛啤酒、白云山、
    # 共同点，首个标题都为审计报告，
    # 3）还有一个像’海康威视‘这种恶心的，一、审计报告，但没有二、财务报表，而是直接 合并资产负债表(居中标题)等三类财报表格，
    # 然后是’财务报表附注‘，再接着(一) 公司基本情况(二) 财务报表的编制基础....
    # 所以不判断第一个审计报告有序号就表示是序号，而要判断有没有’二、财务报表‘才能确定是否为普通带序号的目录

    # # 财报附注的种类型：()
    # i_caibao_fz_count = 0 #财报附注个数，前几个项目顺序是固定的，在5个之前出现后面的title不处理
    # child_fuzu_titles_1 = ['公司基本情况','财务报表的编制基础','重要会计政策及会计估计','重要会计政策和会计估计','主要会计政策和会计估计',
    #                 '税项','合并财务报表项目注释','合并财务报表项目附注','合并财务报表重要项目注释','合并财务报表主要项目注释',
    #                 '研发支出','合并范围的变更','在其他主体中的权益','政府补助','与金融工具相关的风险','公允价值的披露',
    #                 '关联方及关联交易','关联方关系及其交易','股份支付','承诺及或有事项','资产负债表日后事项','资产负债表日后事项说明',
    #                 '其他重要事项','母公司财务报表主要项目注释','补充资料',]
    # child_fuzu_titles_2 = ['基本情况','财务报表的编制基础',
    #                        '重要会计政策及会计估计','重要会计政策及会计估计','重要会计政策和会计估计','主要会计政策和会计估计',
    #                        '税项','合并财务报表项目注释','合并财务报表项目附注','合并财务报表重要项目注释','合并财务报表主要项目注释',
    #                        '研发支出','合并范围的变动','在其他主体中的权益','与金融工具相关的风险',
    #                        '公允价值的披露','关联方关系及其交易','股份支付','承诺及或有事项','资产负债表日后事项','其他重要事项']
    # 合并财务报表项目可能的标题：
    child_hbzs_titles = ['合并财务报表项目注释','合并财务报表项目附注','合并财务报表重要项目注释','合并财务报表主要项目注释',]
    
    child_caiwubb_titles = ['合并资产负债表','母公司资产负债表','合并利润表','母公司利润表','合并现金流量表',
                            '母公司现金流量表','合并股东权益变动表','合并所有者权益变动表','母公司股东权益变动表','母公司所有者权益变动表']
    child_otherbb_titles = ['合并及公司资产负债表','合并及公司利润表','合并及公司现金流量表','合并股东权益变动表',]

    # 青岛啤酒的很坑爹，在财务报告子项目录，没有一、二、三、这样的序号，也没有'财务报表'，'公司基本情况'
    # ca,青岛啤酒叫：合并财务报表项目附注，妹的偏搞个不一样的
    icaiwu_baobiao_item = 0
    has_got_caiwu_bb = False #是否已读取到'财务报表'子目录
    i_offset = i_invalid_childpage_offset if i_invalid_childpage_offset>0 else 1
    ichild_nums_count = 0
    new_toc = []
    i_memo_page_index = 0
    memo_left_lines = []
    while ipage_index < iend_page_num:
        caiwutext = doc.get_page_text(ipage_index)
        caiwulines = caiwutext.split('\n')
        #依次查找child_nums[0],找到一个就del一个
        ipage_num = ipage_index + i_offset
        for index,line in enumerate(caiwulines):
            if is_special_caiwu_bb:
                # 青岛啤酒等特殊财务报表格式，财务报告页本身有个目录，读取该目录即可得到
                line_text = line.strip()
                if line_text == "审计报告" and index+1<len(caiwulines):
                    if len(new_toc)==0 or not any('审计报告' in item[1] for item in new_toc):
                        num_title = caiwulines[index+1]
                        numbers = re.findall(r'\d+', num_title)
                        if len(numbers)>0:
                            ipage_num = int(numbers[0])
                            new_item = [2, line_text, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("财务报告子目录：",new_item)
                elif line_text.endswith("资产负债表") and index+1<len(caiwulines):
                    if len(new_toc)==0 or not any('资产负债表' in item[1] for item in new_toc):
                        num_title = caiwulines[index+1]
                        numbers = re.findall(r'\d+', num_title)
                        if len(numbers)>0:
                            ipage_num = int(numbers[0])
                            new_item = [2, line_text, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("财务报告子目录：",new_item)
                elif line_text.endswith("利润表") and index+1<len(caiwulines):
                    if len(new_toc)==0 or not any('利润表' in item[1] for item in new_toc):
                        num_title = caiwulines[index+1]
                        numbers = re.findall(r'\d+', num_title)
                        if len(numbers)>0:
                            ipage_num = int(numbers[0])
                            new_item = [2, line_text, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("财务报告子目录：",new_item)
                elif line_text.endswith("现金流量表") and index+1<len(caiwulines):
                    if len(new_toc)==0 or not any('现金流量表' in item[1] for item in new_toc):
                        num_title = caiwulines[index+1]
                        numbers = re.findall(r'\d+', num_title)
                        if len(numbers)>0:
                            ipage_num = int(numbers[0])
                            new_item = [2, line_text, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("财务报告子目录：",new_item)
                elif line_text.endswith("权益变动表") and index+1<len(caiwulines):
                    if len(new_toc)==0 or not any('权益变动表' in item[1] for item in new_toc):
                        num_title = caiwulines[index+1]
                        numbers = re.findall(r'\d+', num_title)
                        if len(numbers)>0:
                            ipage_num = int(numbers[0])
                            new_item = [2, line_text, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("财务报告子目录：",new_item)
                elif line_text.endswith("报表附注") and index+1<len(caiwulines):
                    if len(new_toc)==0 or not any('报表附注' in item[1] for item in new_toc):
                        num_title = caiwulines[index+1]
                        numbers = re.findall(r'\d+', num_title)
                        if len(numbers)>0:
                            ipage_num = int(numbers[0])
                            new_item = [2, line_text, ipage_num+i_invalid_pagecount]
                            new_toc.append(new_item)
                            print("财务报告子目录：",new_item)
                elif line_text.endswith("合并财务报表项目附注"):
                    icaiwu_baobiao_item = len(new_toc) #记录在财务报表中插入现金流量表补充资料的位置
                    #开始注释项目
                    new_item = [2, "合并财务报表项目附注", ipage_num+i_invalid_pagecount]
                    new_toc.append(new_item)
                    print("财务报告子目录：",new_item)

                    left_lines = caiwulines[index+1:]
                    memo_toc = find_caiwu_memo_list(doc,ipage_num,left_lines,is_special_caiwu_bb)
                    if len(memo_toc) > 0:
                        new_toc += memo_toc
                        if icaiwu_baobiao_item>0:
                                for item in memo_toc:
                                    if "现金流量表补充资料" in item[1]:
                                        # 将"现金流量表补充资料"记录到财务报表下，以便快捷读取
                                        new_toc.insert(icaiwu_baobiao_item,[2,"现金流量表补充资料",item[2]])
                                        break
            # 非特殊财务报告项目：
            else:
                title = line.strip() #(五)   合并财务报表项目注释 
                # 财务报告子目录
                list_target =  [child_title  for child_title in child_titles if title.endswith(child_title)]
                numbers = query_numbers_in_line(title)
                if len(list_target)>0 and len(title.replace(" ", ""))<len(list_target[0])+5 and 0 == len(numbers):
                    #'考虑其他信息是否与财务报表'错误命中了‘财务报表’，
                    has_pre_num = False
                    if title.startswith(child_nums[0]):
                        ichild_nums_count += 1
                        del child_nums[0]
                        if len(new_toc)>0: #非第一个'审计报告'有序号，后面的也有序号，说明是为的有序号的目录格式
                            has_pre_num = True
                    elif index>0:
                        #标题中没有序号时查看上一个是否为序号
                        preline = caiwulines[index-1].lstrip()
                        if preline.startswith(child_nums[0]):
                            title = preline + title
                            ichild_nums_count += 1
                            del child_nums[0]
                            if len(new_toc)>0:
                                has_pre_num = True
                    
                    if ichild_nums_count > 2 and not has_pre_num: #如有2个以上都有序号，到你这个没有序号，说明可能不是子目录标题，而是某个内容
                        # 存在审计报告有一、，而财务报表却没有二、的情况
                        print("cancel.err.财务报告子目录:",title)
                        continue

                    if icaiwu_baobiao_item == 0: #财报三个表还没读取到时，先出现了child_titles‘财务报表’后面的其他目录标题，略过
                        i_title = child_titles.index(list_target[0]) #
                        if i_title > 0:
                            continue

                    child_titles.remove(list_target[0])

                    if i_memo_page_index>0:
                        # 如果有i_memo_page_index，说明当前这项是‘合并财务报表项目注释’的下一项，当前pageindex为结束index
                        memo_toc = find_caiwu_memo_list(doc,i_memo_page_index,ipage_index+1,memo_left_lines,is_special_caiwu_bb)
                        i_memo_page_index = 0
                        memo_left_lines.clear()
                        if memo_toc is not None and len(memo_toc) > 0:
                            new_toc += memo_toc
                            if icaiwu_baobiao_item>0: #有财务报表子目录时才插入现金表补充，以集中查看
                                    for item in memo_toc:
                                        if "现金流量表补充资料" in item[1]:
                                            # 将"现金流量表补充资料"记录到财务报表下，以便快捷读取
                                            cash_item = [3 if has_got_caiwu_bb else 2, "现金流量表补充资料", item[2]]
                                            new_toc.insert(icaiwu_baobiao_item,cash_item)
                                            print("插入现金表补充：",cash_item)
                                            break

                    new_item = [2, title, ipage_num]
                    new_toc.append(new_item)
                    print("财务报告子目录：",new_item)

                    if list_target[0] == '财务报表':
                        has_got_caiwu_bb = True
                    elif list_target[0] in child_hbzs_titles: # 合并财务报表项目注释
                        if 0 == icaiwu_baobiao_item:
                            icaiwu_baobiao_item = len(new_toc)-1
                            has_got_caiwu_bb = False
                        i_memo_page_index = ipage_index
                        memo_left_lines = caiwulines[index+1:]
                        
                        child_titles = behind_hbfz_titles
                        
                else:
                    # 财务报表子目录
                    list_target =  [child_title  for child_title in child_caiwubb_titles if title.endswith(child_title)]
                    if len(list_target)>0:
                        child_caiwubb_titles.remove(list_target[0])
                        new_item = [3 if has_got_caiwu_bb else 2, title, ipage_num]
                        new_toc.append(new_item)
                        print("财务报表子目录：",new_item)
                        icaiwu_baobiao_item = len(new_toc)
                        # 得到合并负债表、合并利润表、合并现金流量表所在页面，然后读取其表格数据
                        if '财务报表' in child_titles:
                            child_titles.remove('财务报表')
                    else:
                        # 特殊财务报表(合并与母公司在同一表中展示的报表)子目录
                        list_target =  [child_title  for child_title in child_otherbb_titles if title.endswith(child_title)]
                        if len(list_target)>0:
                            child_otherbb_titles.remove(list_target[0])
                            new_item = [3 if has_got_caiwu_bb else 2, title, ipage_num]
                            new_toc.append(new_item)
                            print("特殊财务报表子目录：",new_item)
                            icaiwu_baobiao_item = len(new_toc)
                            if '财务报表' in child_titles:
                                child_titles.remove('财务报表')


            

        # 财务报告本页没有，继续往下面的页面找
        ipage_index += 1

    return new_toc

# 使用函数来修正书签  
sfile = "科大讯飞2020"

script_dir = os.path.dirname(os.path.abspath(__file__))# 构建目标文件相对于脚本所在目录的路径
fix_pdf_bookmarks(os.path.join(script_dir,f"../pdf/{sfile}.pdf"), os.path.join(script_dir,f"../pdf/{sfile}_ok.pdf"))

# 当自动修复不了时，改换手动档，如：重庆啤酒2023
# sfile = "重庆啤酒2023"
# manufix_pdf_bookmarks(f"pdf/{sfile}.PDF", f"pdf/{sfile}_ok.PDF")

# 验证新目录
# sfile = "Python爬虫入门教程：超级简单的Python爬虫教程"
# get_pdf_bookmarks(f"pdf/{sfile}.PDF")

# get_6_year_data('pdf/海康2019_ok.pdf',[123,124],'pdf/海康2021_ok.pdf',[153,154],'pdf/海康2023_ok.pdf',[159,160])